package cn.ipokerface.api;

import cn.ipokerface.api.annotation.*;
import cn.ipokerface.api.model.*;
import cn.ipokerface.api.utils.ClassUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Created by       PokerFace
 * Create Date      2019-12-09.
 * Email:           <a href="mailto:214888341@163.com">214888341@163.com</a>
 * Version          1.0.0
 * <p>
 * Description:
 */
public class PackageScanner {


    private CoreContext context;

    private List<Enumeration> enumerations;

    private List<Group> groupList;

    private Map<Integer, List<Group>> groupMap;

    private List<SmartApi> rootApis;

    private Map<Integer, List<SmartApi>> apiMap;

    private List<Define> defineList;


    public PackageScanner(CoreContext context){
        this.context = context;
        enumerations = new ArrayList<>();
        groupList = new ArrayList<>();
        groupMap = new HashMap<>();
        rootApis = new ArrayList<>();
        apiMap = new HashMap<>();
        defineList = new ArrayList<>();
    }


    /**
     * Scan for cn.ipokerface.api define.
     *
     */
    public ConfigurationHolder scan(){

        String targetPackage = context.getPackageName();

        if (targetPackage == null || "".equals(targetPackage))
            throw new IllegalArgumentException("packageName is not defined...");

        Set<Class<?>> classes = ClassUtils.getClasses(targetPackage);

        if (classes == null || classes.size() == 0) return null;

        for(Class<?> cls: classes){

            ApiPrefix apiPrefix = cls.getAnnotation(ApiPrefix.class);
            String prefix = apiPrefix == null ? "": apiPrefix.value()==null? "": apiPrefix.value();

            Method[] methods = cls.getDeclaredMethods();
            if (methods!= null && methods.length > 0){
                for(Method method: methods){

                    // Scan enums
                    scanEnums(method.getAnnotationsByType(ApiEnum.class));

                    // Scan groups define
                    scanGroups(method.getAnnotationsByType(ApiGroup.class));

                    // scan value define;
                    scanDefines(method.getAnnotationsByType(ApiDefine.class));

                    // Scan apis.
                    scanApis(method, prefix);

                }
            }

            Field[] fields = cls.getDeclaredFields();
            if (fields != null && fields.length > 0){
                for(Field field: fields){
                    field.setAccessible(true);

                    // Scan enums
                    scanEnums(field.getAnnotationsByType(ApiEnum.class));

                    // Scan groups define
                    scanGroups(field.getAnnotationsByType(ApiGroup.class));

                    // scan value define;
                    scanDefines(field.getAnnotationsByType(ApiDefine.class));

                }
            }
        }

        // group list is not defined

        if (groupList.size() > 0){
            for(Group group: groupList){
                resolveGroupRec(group);
            }
        }


        return new ConfigurationHolder(context, enumerations, defineList, groupList, rootApis);

    }


    /**
     * Scan define of enums ...
     *
     * @param apiEnums apiEnums
     */
    private void scanEnums( ApiEnum[] apiEnums){
        if (apiEnums == null || apiEnums.length == 0) return;
        for(ApiEnum apiEnum: apiEnums){

            List<EnumerationValue> values = new ArrayList<>();

            if(apiEnum.value() != null && apiEnum.value().length > 0){
                for(ApiEnumValue apiEnumValue: apiEnum.value()){
                    values.add(new EnumerationValue(apiEnumValue.key(), apiEnumValue.name()));
                }
            }
            enumerations.add(new Enumeration(apiEnum.name(), apiEnum.key(), values));

        }
    }


    /**
     *  Scan defined groups
     *
     * @param groups
     */
    private void scanGroups(ApiGroup[] groups){
        if (groups == null || groups.length == 0) return;
        for(ApiGroup group: groups){
            int id = group.id();
            if (id < 0 ) continue;

            int groupId = group.group();

            Group groupModel = new Group();
            groupModel.setId(id);
            groupModel.setGroup(groupId);
            groupModel.setName(group.name());

            if (groupId < 0) {
                groupList.add(groupModel);
            }else{
                List<Group> childList = groupMap.get(groupId);

                if (childList == null){
                    childList = new ArrayList<>();
                    groupMap.put(groupId, childList);
                }

                childList.add(groupModel);
            }
        }
    }


    private void scanDefines(ApiDefine[] defines) {
        if (defines == null || defines.length == 0) return;

        for(ApiDefine apiDefine: defines) {
            defineList.add(new Define(apiDefine.name(), apiDefine.value()));
        }
    }


    private void scanApis(Method method, String prefix) {

        Api api = method.getAnnotation(Api.class);
        if (api == null) return;

        int group = api.group();

        SmartApi smartApi = new SmartApi(api.name(), concatUrl(prefix, api.url()),api.group(), api.method(), api.response());

        if (group < 0) {
            rootApis.add(smartApi);
        }
        else{
            List<SmartApi> childList = apiMap.get(group);

            if (childList == null) {
                childList = new ArrayList<>();
                apiMap.put(group, childList);
            }

            childList.add(smartApi);
        }

        ApiHeader[] apiHeaders = method.getAnnotationsByType(ApiHeader.class);
        if (apiHeaders != null && apiHeaders.length > 0 ){
            List<Header> headers = new ArrayList<>();
            for (ApiHeader header: apiHeaders) {
                headers.add(new Header(header.name(), header.key(), header.options()));
            }
            smartApi.setHeaderList(headers);
        }

        ApiParameter[] apiParameters = method.getAnnotationsByType(ApiParameter.class);
        if (apiParameters != null && apiParameters.length > 0) {
            List<Parameter> rootParameter = new ArrayList<>();
            Map<String, List<Parameter>> parameterMap = new HashMap<>();
            Map<Parameter, String> parameterDefine = new HashMap<>();

            for(ApiParameter apiParameter: apiParameters) {
                Parameter parameter = new Parameter(
                        apiParameter.key(),
                        apiParameter.name(),
                        apiParameter.required(),
                        apiParameter.type(),
                        apiParameter.remark(),
                        apiParameter.options()
                );

                String propertyOf = apiParameter.propertyOf();
                if (propertyOf == null || "".equals(propertyOf)) {
                    rootParameter.add(parameter);
                }else{
                    List<Parameter> childParameter = parameterMap.get(propertyOf);
                    if (childParameter == null){
                        childParameter = new ArrayList<>();
                        parameterMap.put(propertyOf, childParameter);
                    }
                    childParameter.add(parameter);
                }

                String define = apiParameter.define();
                if (define != null && !"".equals(define)) {
                    parameterDefine.put(parameter, define);
                }
            }

            resolveParameterRec(rootParameter, parameterMap, parameterDefine);

            smartApi.setParameterList(rootParameter);
        }


        ApiResponse[] apiResponses = method.getAnnotationsByType(ApiResponse.class);
        if (apiResponses != null && apiResponses.length > 0) {
            List<Response> rootResponse = new ArrayList<>();
            Map<String, List<Response>> responseMap = new HashMap<>();
            Map<Response, String> responseDefine = new HashMap<>();

            for(ApiResponse apiResponse: apiResponses) {
                Response response = new Response(
                        apiResponse.key(),
                        apiResponse.name(),
                        apiResponse.contains(),
                        apiResponse.type(),
                        apiResponse.remark(),
                        apiResponse.options()
                );

                String propertyOf = apiResponse.propertyOf();
                if (propertyOf == null || "".equals(propertyOf)) {
                    rootResponse.add(response);
                }else{
                    List<Response> childParameter = responseMap.get(propertyOf);
                    if (childParameter == null){
                        childParameter = new ArrayList<>();
                        responseMap.put(propertyOf, childParameter);
                    }
                    childParameter.add(response);
                }

                String define = apiResponse.define();
                if (define != null && !"".equals(define)) {
                    responseDefine.put(response, define);
                }
            }

            resolveResponseRec(rootResponse, responseMap, responseDefine);

            smartApi.setResponseList(rootResponse);
        }

    }



    private void  resolveGroupRec(Group parent){

        List<Group> childList = groupMap.get(parent.getId());
        if (childList != null && childList.size() > 0) {
            for(Group group:childList){
                resolveGroupRec(group);
            }

            parent.setChildren(childList);
        }

        List<SmartApi> smartApiList = apiMap.get(parent.getId());
        if (smartApiList != null && smartApiList.size() > 0){
            parent.setSmartApis(smartApiList);
        }
    }





    private void resolveParameterRec(List<Parameter> parameters, Map<String,
            List<Parameter>> parameterMap, Map<Parameter, String> parameterDefine) {
        if (parameters == null || parameters.size() == 0) return;

        for(Parameter parameter: parameters) {
            if (!Type.Model.equals(parameter.getType()) && !Type.ModelList.equals(parameter.getType())) continue;

            String define = parameterDefine.get(parameter);
            if (define == null) continue;

            List<Parameter> childList = parameterMap.get(define);
            if (childList == null || childList.size() == 0) continue;

            parameter.setProperties(childList);

            resolveParameterRec(childList, parameterMap, parameterDefine);
        }
    }

    private void resolveResponseRec(List<Response> responses, Map<String,
            List<Response>> responseMap, Map<Response, String> responseDefine) {
        if (responses == null || responses.size() == 0) return;

        for(Response response: responses) {
            if (!Type.Model.equals(response.getType()) && !Type.ModelList.equals(response.getType())) continue;
            String define = responseDefine.get(response);
            if (define == null) continue;

            List<Response> childList = responseMap.get(define);
            if (childList == null || childList.size() == 0) continue;

            response.setProperties(childList);

            resolveResponseRec(childList, responseMap, responseDefine);
        }
    }


    private String concatUrl(String prefix, String uri){
        if (!prefix.startsWith("/")) prefix = "/" + prefix;

        if (prefix.endsWith("/")) prefix = prefix.substring(0, prefix.length() - 1);

        if (uri.startsWith("/")) {
            return prefix + uri;
        }else{
            return prefix + "/" + uri;
        }
    }

}
